// LineTracker.cpp: implementation of the CLineTracker class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "LineTracker.h"
#include <math.h>

static COLORREF const CLR_WHITE = RGB(255, 255, 255);

#define _POLYC_SIZE 30

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CLineTracker::CLineTracker()
{
	m_nHandleSize = 5;
}

CLineTracker::~CLineTracker()
{
}

BOOL CLineTracker::SetCursor(CWnd* pWnd, UINT nHitTest)
{
	// trackers should only be in client area
	if (nHitTest != HTCLIENT)
		return FALSE;

	// convert cursor position to client co-ordinates
	CPoint point;
	GetCursorPos(&point);
	pWnd->ScreenToClient(&point);

	// do hittest and normalize hit
	switch (HitTest(point))
	{
	case hitBegin:
		::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE));
		break;

	case hitEnd:
		::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE));
		break;

	case hitMiddle:
		::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEALL));
		break;

	default:
		return FALSE;
	};

	return TRUE;
}

BOOL CLineTracker::TrackRubberBand(CWnd* pWnd, CPoint point)
{
	m_rect.left = m_rect.right = point.x;
	m_rect.top = m_rect.bottom = point.y;
	return TrackHandle(hitEnd, pWnd, point);
}

void CLineTracker::GetHandleRect(int nHandle, CRect* pHandleRect)
{
	if (nHandle == hitBegin)
	{
		pHandleRect->left = m_rect.left - m_nHandleSize / 2;
		pHandleRect->top = m_rect.top - m_nHandleSize / 2;
	}
	else
	{
		pHandleRect->left = m_rect.right - m_nHandleSize / 2;
		pHandleRect->top = m_rect.bottom - m_nHandleSize / 2;
	};

	pHandleRect->right = pHandleRect->left + m_nHandleSize;
	pHandleRect->bottom = pHandleRect->top + m_nHandleSize;
}

int CLineTracker::HitTest(CPoint point)
{
	// check handles
	CRect R;
	GetHandleRect(hitBegin, &R);
	if (R.PtInRect(point))
		return hitBegin;

	GetHandleRect(hitEnd, &R);
	if (R.PtInRect(point))
		return hitEnd;

	R = m_rect;
	R.NormalizeRect();
	CRect VicX = R;
	CRect VicY = R;

	int nVicinitySize = m_nHandleSize*2;
	VicX.InflateRect(nVicinitySize, 0);
	VicY.InflateRect(0, nVicinitySize);

	if (!VicX.PtInRect(point) && !VicY.PtInRect(point))
		return hitNothing;

	// check distance from the point to the line by x and by y
	int dx = m_rect.Width();
	int dy = m_rect.Height();
	double r = sqrt((double)(dx* dx + dy* dy));

	// compute coordinates relative to the origin of the line
	int x1 = point.x - m_rect.left;
	int y1 = point.y - m_rect.top;

	// compute coordinates relative to the line
	double x2 = (x1* dx + y1* dy) / r;
	double y2 = (-x1* dy + y1* dx) / r;

	if ((x2 >= 0) &&
		(x2 <= r) &&
		(y2 <= nVicinitySize) &&
		(y2 >= -nVicinitySize))
		return hitMiddle;

	return hitNothing;
}

BOOL CLineTracker::TrackHandle(int nHandle, CWnd* pWnd, CPoint point,
	CWnd* pWndClipTo /* = NULL */)
{
	if ((nHandle != hitBegin) && (nHandle != hitEnd) && (nHandle != hitMiddle))
		return FALSE;

	ASSERT(nHandle >= 0);
	ASSERT(nHandle <= 8);   // handle 8 is inside the rect

	// don't handle if capture already set
	if (::GetCapture() != NULL)
		return FALSE;

	// AfxLockTempMaps();  // protect maps while looping

	BOOL m_bFinalErase = FALSE;
	ASSERT(!m_bFinalErase);

	// save original width & height in pixels
	int nWidth = m_rect.Width();
	int nHeight = m_rect.Height();

	// set capture to the window which received this message
	pWnd->SetCapture();
	ASSERT(pWnd == CWnd::GetCapture());
	pWnd->UpdateWindow();
	if (pWndClipTo != NULL)
		pWndClipTo->UpdateWindow();
	CRect rectSave = m_rect;

	// find out what x/y coords we are supposed to modify
	CPoint pt;
	// 	xDiff = point.x - xDiff;
	// 	yDiff = point.y - yDiff;

	// get DC for drawing
	CDC* pDrawDC;
	if (pWndClipTo != NULL)
	{
		// clip to arbitrary window by using adjusted Window DC
		pDrawDC = pWndClipTo->GetDCEx(NULL, DCX_CACHE);
	}
	else
	{
		// otherwise, just use normal DC
		pDrawDC = pWnd->GetDC();
	}
	ASSERT_VALID(pDrawDC);

	// set dc parameters
	CPen pen(PS_SOLID, 1, CLR_WHITE);
	CPen* pOldPen = pDrawDC->SelectObject(&pen);
	CBrush* pOldBr = (CBrush*) pDrawDC->SelectStockObject(NULL_BRUSH);
	int nROP2 = pDrawDC->SetROP2(R2_XORPEN);

	DrawDragRect(pDrawDC, m_rect);

	CRect rectOld;
	BOOL bMoved = FALSE;

	// get messages until capture lost or cancelled/accepted
	for (; ;)
	{
		MSG msg;
		VERIFY(::GetMessage(&msg, NULL, 0, 0));

		if (CWnd::GetCapture() != pWnd)
			break;

		switch (msg.message)
		{
			// handle movement/accept messages
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:
			rectOld = m_rect;

			// handle resize cases (and part of move)
			pt.x = (int) (short) LOWORD(msg.lParam);
			pt.y = (int) (short) HIWORD(msg.lParam);

			// handle move case
			if (nHandle == hitMiddle)
			{
				m_rect.left = rectOld.left + (pt.x - point.x);
				m_rect.top = rectOld.top + (pt.y - point.y);
				m_rect.right = m_rect.left + nWidth;
				m_rect.bottom = m_rect.top + nHeight;
			}
			else if (nHandle == hitBegin)
			{
				m_rect.left = pt.x;
				m_rect.top = pt.y;
			}
			else if (nHandle == hitEnd)
			{
				m_rect.right = pt.x;
				m_rect.bottom = pt.y;
			}

			// allow caller to adjust the rectangle if necessary
			//			AdjustRect(nHandle, &m_rect);

			// only redraw and callback if the rect actually changed!
			m_bFinalErase = (msg.message == WM_LBUTTONUP);
			if (!rectOld.EqualRect(&m_rect) || m_bFinalErase)
			{
				if (bMoved)
				{
					// m_bErase = TRUE;
					// 					pDrawDC->MoveTo(rectOld.TopLeft());
					// 					pDrawDC->LineTo(rectOld.BottomRight());
					DrawDragRect(pDrawDC, rectOld);
				}
				// OnChangedRect(rectOld);
				if (msg.message != WM_LBUTTONUP)
					bMoved = TRUE;
			}

			if (m_bFinalErase)
				goto ExitLoop;

			if (!rectOld.EqualRect(&m_rect))
			{
				// m_bErase = FALSE;
				// 				pDrawDC->MoveTo(m_rect.TopLeft());
				// 				pDrawDC->LineTo(m_rect.BottomRight());
				DrawDragRect(pDrawDC, m_rect);
			}
			break;
			// handle cancel messages
		case WM_KEYDOWN:
			if (msg.wParam != VK_ESCAPE)
				break;
		case WM_RBUTTONDOWN:
			if (bMoved)
			{
				m_bFinalErase = TRUE;
				pDrawDC->MoveTo(m_rect.TopLeft());
				pDrawDC->LineTo(m_rect.BottomRight());
			}
			m_rect = rectSave;
			goto ExitLoop;

			// just dispatch rest of the messages
		default:
			DispatchMessage(&msg);
			break;
		}
	}

ExitLoop : 
	pDrawDC->SetROP2(nROP2);
	pDrawDC->SelectObject(pOldBr);
	pDrawDC->SelectObject(pOldPen);
	if (pWndClipTo != NULL)
		pWndClipTo->ReleaseDC(pDrawDC);
	else
		pWnd->ReleaseDC(pDrawDC);
	ReleaseCapture();

	// restore rect in case bMoved is still FALSE
	if (!bMoved)
		m_rect = rectSave;
	m_bFinalErase = FALSE;

	// return TRUE only if rect has changed
	return !rectSave.EqualRect(&m_rect);
}

void CLineTracker::DrawDragRect(CDC* pDC, LPRECT lpRect)
{
	if ((lpRect->left != lpRect->right) || (lpRect->top != lpRect->bottom))
	{
		pDC->MoveTo(lpRect->left, lpRect->top);
		pDC->LineTo(lpRect->right, lpRect->bottom);
	}
}

void CEllipseTracker::DrawDragRect(CDC* pDC, LPRECT lpRect)
{
	pDC->Ellipse(lpRect);
}

BOOL CPolylineTracker::TrackHandle(int nHandle, CWnd* pWnd, CPoint point,
	CWnd* pWndClipTo /* = NULL */)
{
	if ((nHandle != hitBegin) && (nHandle != hitEnd) && (nHandle < hitMiddle))
		return FALSE;

	ASSERT(nHandle >= 0);
	// ASSERT(nHandle <= 8);   // handle 8 is inside the rect

	// don't handle if capture already set
	if (::GetCapture() != NULL)
		return FALSE;

	int nIndex = -1;
	if (nHandle > 8)
		nIndex = nHandle - 9;

	// AfxLockTempMaps();  // protect maps while looping

	BOOL m_bFinalErase = FALSE;
	ASSERT(!m_bFinalErase);

	// save original width & height in pixels
	int nWidth = m_rect.Width();
	int nHeight = m_rect.Height();

	// set capture to the window which received this message
	pWnd->SetCapture();
	ASSERT(pWnd == CWnd::GetCapture());
	pWnd->UpdateWindow();
	if (pWndClipTo != NULL)
		pWndClipTo->UpdateWindow();
	CRect rectSave = m_rect;

	// find out what x/y coords we are supposed to modify
	CPoint pt;

	// get DC for drawing
	CDC* pDrawDC;
	if (pWndClipTo != NULL)
	{
		// clip to arbitrary window by using adjusted Window DC
		pDrawDC = pWndClipTo->GetDCEx(NULL, DCX_CACHE);
	}
	else
	{
		// otherwise, just use normal DC
		pDrawDC = pWnd->GetDC();
	}
	ASSERT_VALID(pDrawDC);

	// set dc parameters
	CPen pen(PS_SOLID, 1, CLR_WHITE);
	CPen* pOldPen = pDrawDC->SelectObject(&pen);
	CBrush* pOldBr = (CBrush*) pDrawDC->SelectStockObject(NULL_BRUSH);
	int nROP2 = pDrawDC->SetROP2(R2_XORPEN);
	// 	BOOL bThree = FALSE;
	// 	if ((nIndex > 0) && (nIndex < (m_points.GetSize()-1)))
	// 		bThree = TRUE;

	if (nIndex < 0)
	{
		m_points.RemoveAll();
		m_points.Add(m_rect.TopLeft());
		DrawDragRect(pDrawDC, m_rect);
	}
	else if (m_bThree)
	{
		CRect rect(m_points[0], m_points[1]);
		DrawDragRect(pDrawDC, rect);
		rect = CRect(m_points[1], m_points[2]);
		DrawDragRect(pDrawDC, rect);
	}
	else if (m_points.GetSize() > 1)
	{
		if (nIndex)
			nIndex = 1;

		CRect rect(m_points[nIndex], m_points[1 - nIndex]);
		DrawDragRect(pDrawDC, rect);
	}

	CRect rectOld;
	BOOL bMoved = FALSE;

#ifdef _POLYC_SIZE
	BOOL bDown = TRUE;
#endif

	// get messages until capture lost or cancelled/accepted
	for (; ;)
	{
		MSG msg;
		VERIFY(::GetMessage(&msg, NULL, 0, 0));

		if (CWnd::GetCapture() != pWnd)
			break;

		switch (msg.message)
		{
		case WM_LBUTTONDBLCLK:
			if (nHandle < hitMiddle)
			{
				//m_points.Add(m_rect.BottomRight());
				goto ExitLoop;
			}
			break;
		case WM_LBUTTONDOWN:
			if (nHandle < hitMiddle)
			{
#ifdef _POLYC_SIZE
				bDown = TRUE;
#endif
				m_rect.left = m_rect.right;
				m_rect.top = m_rect.bottom;
				m_points.Add(m_rect.TopLeft());
			}
			break;
			// handle movement/accept messages
		case WM_LBUTTONUP:
#ifdef _POLYC_SIZE
			bDown = FALSE;
#endif
		case WM_MOUSEMOVE:
			m_bFinalErase = (msg.message == WM_LBUTTONUP);
			if ((nHandle < 9) && (m_bFinalErase))
				break;

			rectOld = m_rect;

			// handle resize cases (and part of move)
			pt.x = (int) (short) LOWORD(msg.lParam);
			pt.y = (int) (short) HIWORD(msg.lParam);

			// handle move case
			if (nHandle == hitMiddle)
			{
				m_rect.left = rectOld.left + (pt.x - point.x);
				m_rect.top = rectOld.top + (pt.y - point.y);
				m_rect.right = m_rect.left + nWidth;
				m_rect.bottom = m_rect.top + nHeight;
			}
			else if (nHandle == hitBegin)
			{
				m_rect.left = pt.x;
				m_rect.top = pt.y;
			}
			else if (nHandle == hitEnd)
			{
				m_rect.right = pt.x;
				m_rect.bottom = pt.y;
			}

			// allow caller to adjust the rectangle if necessary
			//			AdjustRect(nHandle, &m_rect);

			// only redraw and callback if the rect actually changed!
			if (nHandle > hitMiddle)
			{
				if (m_bThree)
				{
					if (m_points[1] != pt)
					{
						CRect rect(m_points[0], m_points[1]);
						DrawDragRect(pDrawDC, rect);
						rect = CRect(m_points[1], m_points[2]);
						DrawDragRect(pDrawDC, rect);

						m_points[1].x = pt.x;
						m_points[1].y = pt.y;

						if (m_bFinalErase)
							goto ExitLoop;

						rect = CRect(m_points[0], m_points[1]);
						DrawDragRect(pDrawDC, rect);
						rect = CRect(m_points[1], m_points[2]);
						DrawDragRect(pDrawDC, rect);
						bMoved = TRUE;
					}
				}
				else if (m_points.GetSize() > 1)
				{
					if (m_points[nIndex] != pt)
					{
						CRect rect(m_points[nIndex], m_points[1 - nIndex]);
						DrawDragRect(pDrawDC, rect);

						m_points[nIndex].x = pt.x;
						m_points[nIndex].y = pt.y;

						if (m_bFinalErase)
							goto ExitLoop;

						rect = CRect(m_points[nIndex], m_points[1 - nIndex]);
						DrawDragRect(pDrawDC, rect);
						bMoved = TRUE;
					}
				}

				if (m_bFinalErase)
					goto ExitLoop;
			}
			else
			{
				if (!rectOld.EqualRect(&m_rect))
				{
					DrawDragRect(pDrawDC, rectOld);
					// OnChangedRect(rectOld);
					DrawDragRect(pDrawDC, m_rect);
					bMoved = TRUE;
#ifdef _POLYC_SIZE
					if (bDown)
					{
						if (abs(m_rect.Width() * m_rect.Height()) >
							_POLYC_SIZE)
						{
							m_rect.left = m_rect.right;
							m_rect.top = m_rect.bottom;
							m_points.Add(m_rect.TopLeft());
						}
					}
#endif
				}
			}
			break;
			// handle cancel messages
		case WM_KEYDOWN:
			if (msg.wParam != VK_ESCAPE)
				break;
		case WM_RBUTTONDOWN:
			if (bMoved)
			{
				m_bFinalErase = TRUE;
				pDrawDC->MoveTo(m_rect.TopLeft());
				pDrawDC->LineTo(m_rect.BottomRight());
			}
			m_rect = rectSave;
			goto ExitLoop;

			// just dispatch rest of the messages
		default:
			DispatchMessage(&msg);
			break;
		}
	}

ExitLoop : 
	pDrawDC->SetROP2(nROP2);
	pDrawDC->SelectObject(pOldBr);
	pDrawDC->SelectObject(pOldPen);
	if (pWndClipTo != NULL)
		pWndClipTo->ReleaseDC(pDrawDC);
	else
		pWnd->ReleaseDC(pDrawDC);
	ReleaseCapture();

	// restore rect in case bMoved is still FALSE
	if (!bMoved)
		m_rect = rectSave;
	else
	{
		int i = 0;
		if (nHandle > hitMiddle)
		{
			m_rect = CRect(m_points[0], m_points[0]);
			//TRACE(_T("x %d y %d\n"), m_points[i].x, m_points[i].y);
			i ++;
		}

		for (; i < m_points.GetSize(); i++)
		{
			//TRACE(_T("x %d y %d\n"), m_points[i].x, m_points[i].y);
			if (m_points[i].x < m_rect.left)
				m_rect.left = m_points[i].x;
			else if (m_points[i].x > m_rect.right)
				m_rect.right = m_points[i].x;

			if (m_points[i].y < m_rect.top)
				m_rect.top = m_points[i].y;
			else if (m_points[i].y > m_rect.bottom)
				m_rect.bottom = m_points[i].y;
		}

		//TRACE(_T("l %d r %d t %d b %d\n"), m_rect.left, m_rect.right, m_rect.top, m_rect.bottom);
		if (nHandle > hitMiddle)
			return TRUE;
	}
	m_bFinalErase = FALSE;

	// return TRUE only if rect has changed
	return !rectSave.EqualRect(&m_rect);
}